package net.mayee.alice.filter;

import com.google.common.base.Objects;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.props.Props;
import net.mayee.alice.common.Definiens;
import net.mayee.alice.entity.admin.account.Role;
import net.mayee.alice.entity.admin.account.User;
import net.mayee.alice.entity.permission.Action;
import net.mayee.alice.entity.permission.Permission;
import net.mayee.alice.service.admin.account.RoleService;
import net.mayee.alice.service.admin.account.UserService;
import net.mayee.alice.spring.shiro.AliceHashedCredentialsMatcher;
import net.mayee.common.AliceHelper;
import net.mayee.common.PermissionHelper;
import net.mayee.common.utils.Encodes;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ShiroAdminDbRealm extends AuthorizingRealm {

    private static final Logger LOGGER = LoggerFactory.getLogger(ShiroAdminDbRealm.class);
    @Autowired
    protected UserService userService;
    @Autowired
    protected RoleService roleService;

    @Autowired
    private HttpServletRequest request;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authcToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        String loginName = token.getUsername();
        User user = userService.getByLoginName(loginName);
        if (user != null) {
            if (user.getStatusCode() == Definiens.UNAVA_VAL) {
                throw new DisabledAccountException();
            }

            byte[] salt = Encodes.decodeHex(user.getSalt());
            return new SimpleAuthenticationInfo(
                    new ShiroUser(
                            user.getUuid(),
                            user.getLoginName(),
                            user.getName(),
                            user.getPhotoImg(),
                            user.getLanguage().getCode(),
                            user.getStatusCode()
                    ),
                    user.getLoginPassword(), ByteSource.Util.bytes(salt),
                    getName());
        } else {
            throw new UnknownAccountException();
        }

    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        User user = userService.getByLoginName(shiroUser.loginName);
        if(user != null){
            if(user.getStatusCode() == Definiens.STATUS_CODE_USER_ADMIN
                    || user.getStatusCode() == Definiens.STATUS_CODE_USER_SUPER_ADMIN){
                Map<String, Permission> permissionsMap = PermissionHelper.getInstance().getPermissionsMap();
                Set<String> keySet = permissionsMap.keySet();
                for (String menuId : keySet) {
                    Permission permission = permissionsMap.get(menuId);
                    List<Action> list = permission.getActionList();
                    if (list.size() > 0) {
                        for (Action action : list) {
                            info.addStringPermission(menuId + ":" + action.getKey());
                        }
                    }
                }
            }else{
                List<Role> roleList = roleService.getRolesByUser(user.getUuid());
                for (Role role : roleList) {
                    info.addRole(role.getRoleKey());
                    String permissionsString = role.getPermissions();
                    if(permissionsString == null){
                        continue;
                    }
                    String[] permissionsList = permissionsString.split("\\,");
                    for(String permission : permissionsList){
                        String[] per = permission.split("\\:");
                        if(per.length == 2){
                            String[] keys = per[1].split("\\|");
                            for(String k : keys){
                                info.addStringPermission(per[0] + ":" + k);
                            }
                        }
                    }
                }
            }
        }
        return info;
    }

    /**
     * 更新用户授权信息缓存.
     */
    public void clearCachedAuthorizationInfo(Object principal) {
        SimplePrincipalCollection principals = new SimplePrincipalCollection(
                principal, getName());
        clearCachedAuthorizationInfo(principals);
    }

    /**
     * 清除所有用户授权信息缓存.
     */
    public void clearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            for (Object key : cache.keys()) {
                cache.remove(key);
            }
        }
    }

    /**
     * 清除某些用户授权信息缓存.
     */
    public void clearSomeCachedAuthorizationInfo(List<String> names) {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            for(String name : names){
                cache.remove(name);
            }
        }
    }

    /**
     * 清除某个用户授权信息缓存.
     */
    public void clearUserCachedAuthorizationInfo(String name) {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            cache.remove(name);
        }
    }

    public static boolean clearUserCachedAuthorizationInfo(HttpServletRequest request, String name) {
        try {
            WebApplicationContext ctx = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(request.getSession()
                            .getServletContext());
            ctx.getBean(ShiroAdminDbRealm.class).clearUserCachedAuthorizationInfo(name);
            return true;
        } catch (Exception e) {
            LOGGER.error("**** clearUserCachedAuthorizationInfo fail! ****", e);
            return false;
        }
    }

    public static boolean removeAuthCacheForUsers(
            HttpServletRequest request, List<User> userList) {
        try {
            WebApplicationContext ctx = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(request.getSession()
                            .getServletContext());
            ShiroAdminDbRealm sa = ctx.getBean(ShiroAdminDbRealm.class);
            for(User u : userList){
                sa.clearCachedAuthorizationInfo(new ShiroUser(u.getLoginName()));
            }
            return true;
        } catch (Exception e) {
            LOGGER.error("**** removeAuthCacheForUsers fail! ****", e);
            return false;
        }
    }

    /**
     * 通过登录名移除权限缓存
     * @param request
     * @param loginName
     * @return
     */
    public static boolean removeAuthCacheByLoginName(
            HttpServletRequest request, String loginName) {
        try {
            WebApplicationContext ctx = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(request.getSession()
                            .getServletContext());
            ctx.getBean(ShiroAdminDbRealm.class).clearCachedAuthorizationInfo(
                    new ShiroUser(loginName));
            return true;
        } catch (Exception e) {
            LOGGER.error("**** removeAuthCacheByLoginName fail! ****", e);
            return false;
        }
    }

    public static boolean clearAllCachedAuthorizationInfo(
            HttpServletRequest request) {
        try {
            WebApplicationContext ctx = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(request.getSession()
                            .getServletContext());
            ctx.getBean(ShiroAdminDbRealm.class)
                    .clearAllCachedAuthorizationInfo();
            return true;
        } catch (Exception e) {
            LOGGER.error("**** clearAllCachedAuthorizationInfo fail! ****", e);
            return false;
        }
    }

    public static boolean clearSomeCachedAuthorizationInfo(
            HttpServletRequest request, List<String> names) {
        try {
            WebApplicationContext ctx = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(request.getSession()
                            .getServletContext());
            ctx.getBean(ShiroAdminDbRealm.class).clearSomeCachedAuthorizationInfo(names);
            return true;
        } catch (Exception e) {
            LOGGER.error("**** clearSomeCachedAuthorizationInfo fail! ****", e);
            return false;
        }
    }

    public static ShiroUser getLoginUser() {
        return (ShiroUser) SecurityUtils.getSubject().getPrincipal();
    }

    @PostConstruct
    public void initCredentialsMatcher() {
        Props props = AliceHelper.getInstance().getJoddProps();
        String algorithm = props.getValue("encrypt.hash.algorithm");
        int interations = Integer.parseInt(props.getValue("encrypt.hash.interations"));
        AliceHashedCredentialsMatcher matcher = new AliceHashedCredentialsMatcher(algorithm);
        matcher.setHashIterations(interations);

        setCredentialsMatcher(matcher);
    }

    public static class ShiroUser implements Serializable {
        private static final long serialVersionUID = -1373760761780840081L;
        private String uuid;
        private String loginName;
        private String name;
        private String photo;
        private String lagCode;
        private int statusCode;
        private int isLockScreen = 0;

        public ShiroUser(String uuid, String loginName, String name, String photo, String lagCode, int statusCode) {
            this.uuid = uuid;
            this.loginName = loginName;
            this.name = name;
            this.photo = photo;
            this.lagCode = lagCode;
            this.statusCode = statusCode;
        }

        public String getPhoto() {
            return photo;
        }

        public int getIsLockScreen() {
            return isLockScreen;
        }

        public void setIsLockScreen(int isLockScreen) {
            this.isLockScreen = isLockScreen;
        }

        public void setPhoto(String photo) {
            this.photo = photo;
        }

        public void setStatusCode(int statusCode) {
            this.statusCode = statusCode;
        }

        public int getStatusCode() {
            return statusCode;
        }

        public String getUuid() {
            return uuid;
        }

        public ShiroUser(String loginName) {
            this.loginName = loginName;
        }

        public String getName() {
            return name;
        }

        public String getLoginName() {
            return loginName;
        }

        public String getLagCode() {
            return lagCode;
        }

        public void setLagCode(String lagCode) {
            this.lagCode = lagCode;
        }

        /**
         * <shiro:principal/>
         */
        @Override
        public String toString() {
            return loginName;
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(loginName);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ShiroUser other = (ShiroUser) obj;
            if (loginName == null) {
                if (other.loginName != null)
                    return false;
            } else if (!loginName.equals(other.loginName))
                return false;
            return true;
        }
    }

    /**
     * @return 得到过滤器链
     */
    // public static DefaultFilterChainManager getFilterChainManager() {
    // WebApplicationContext ctx = WebApplicationContextUtils
    // .getRequiredWebApplicationContext(request.getSession()
    // .getServletContext());
    // return ((DefaultFilterChainManager) ((PathMatchingFilterChainResolver)
    // SpringContextHolder
    // .getBean(AbstractShiroFilter.class).getFilterChainResolver())
    // .getFilterChainManager());
    // }
}
